package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult searchByParam(RequestParams param) throws Exception {
        SearchRequest searchRequest = new SearchRequest ("hotel");

        basicQueryBuild (searchRequest , param);

        //根据地理位置排序
        //searchRequest.source ().sort ("price", SortOrder.DESC);
        if (StringUtils.isNotBlank (param.getLocation ())) {
            searchRequest.source ().sort (SortBuilders.geoDistanceSort ("location" , new GeoPoint (param.getLocation ())).order (SortOrder.ASC).unit (DistanceUnit.KILOMETERS));
        }
        //设置分页
        Integer from = (param.getPage () - 1) * param.getSize ();
        searchRequest.source ().from (from);
        searchRequest.source ().size (param.getSize ());

        HighlightBuilder highlightBuilder = new HighlightBuilder ();
        highlightBuilder.field ("name");
        highlightBuilder.preTags ("<em style='color:red'>");
        highlightBuilder.postTags ("</em>");
        highlightBuilder.requireFieldMatch (false);

        searchRequest.source ().highlighter (highlightBuilder);

        SearchResponse searchResponse = client.search (searchRequest , RequestOptions.DEFAULT);
        return handleResponse (searchResponse);
    }

    @Override
    public Map<String, List<String>> filters(RequestParams params) throws Exception {

        Map<String,List<String>> resultMap=new HashMap<> ();

        SearchRequest searchRequest = new SearchRequest ("hotel");
        basicQueryBuild (searchRequest , params);
        searchRequest.source ().size (0);

        searchRequest.source ().aggregation (AggregationBuilders.terms ("cityAgg").field ("city").size (100));
        searchRequest.source ().aggregation (AggregationBuilders.terms ("starNameAgg").field ("starName").size (100));
        searchRequest.source ().aggregation (AggregationBuilders.terms ("brandAgg").field ("brand").size (100));

        SearchResponse searchResponse = client.search (searchRequest , RequestOptions.DEFAULT);

        Aggregations aggregations = searchResponse.getAggregations ();

        Terms brandAgg = aggregations.get ("brandAgg");
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets ();
        List<String> brandList = new ArrayList<> ();
        for (Terms.Bucket bucket : buckets) {
            brandList.add (bucket.getKeyAsString ());
        }
        resultMap.put ("brand",brandList);

        Terms starNameAgg = aggregations.get ("starNameAgg");
        List<? extends Terms.Bucket> starNameBuckets = starNameAgg.getBuckets ();
        List<String> starNameList = new ArrayList<> ();
        for (Terms.Bucket bucket : starNameBuckets) {
            starNameList.add (bucket.getKeyAsString ());
        }
        resultMap.put ("starName",starNameList);

        Terms cityAgg = aggregations.get ("cityAgg");
        List<? extends Terms.Bucket> cityAggBuckets = cityAgg.getBuckets ();
        List<String> cityAggList = new ArrayList<> ();
        for (Terms.Bucket bucket : cityAggBuckets) {
            cityAggList.add (bucket.getKeyAsString ());
        }
        resultMap.put ("city",cityAggList);

        return resultMap;
    }

    private void basicQueryBuild(SearchRequest searchRequest , RequestParams param) {

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery ();

        //判断关键字是否为空
        if (StringUtils.isBlank (param.getKey ())) {
            //为空就查询所有
            //searchRequest.source ().query(QueryBuilders.matchAllQuery ());
            boolQuery.must (QueryBuilders.matchAllQuery ());
        } else {
            //searchRequest.source ().query (QueryBuilders.matchQuery ("all",param.getKey ()));
            boolQuery.must (QueryBuilders.matchQuery ("all" , param.getKey ()));
        }
        //判断品牌是否为空
        if (StringUtils.isNotBlank (param.getBrand ())) {
            boolQuery.filter (QueryBuilders.termQuery ("brand" , param.getBrand ()));
        }
        //判断城市是否为空
        if (StringUtils.isNotBlank (param.getCity ())) {
            boolQuery.filter (QueryBuilders.termQuery ("city" , param.getCity ()));
        }
        //判断星级是否为空
        if (StringUtils.isNotBlank (param.getStarName ())) {
            boolQuery.filter (QueryBuilders.termQuery ("starName" , param.getStarName ()));
        }
        //判断价格是否为空
        if (param.getMinPrice () != null && param.getMaxPrice () != null) {
            boolQuery.filter (QueryBuilders.rangeQuery ("price").gte (param.getMinPrice ()).lte (param.getMaxPrice ()));
        }

        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery (
                        // 原始查询，相关性算分的查询
                        boolQuery ,
                        // function score的数组
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                // 其中的一个function score 元素
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder (
                                        // 过滤条件
                                        QueryBuilders.termQuery ("isAD" , true) ,
                                        // 算分函数
                                        ScoreFunctionBuilders.weightFactorFunction (10)
                                )
                        });

        searchRequest.source ().query (functionScoreQuery);

    }


    private PageResult handleResponse(SearchResponse searchResponse) {
        SearchHits searchHits = searchResponse.getHits ();
        long total = searchHits.getTotalHits ().value;
        System.out.println ("查询到总数为:" + total);

        SearchHit[] hits = searchHits.getHits ();

        List<HotelDoc> list = new ArrayList<> ();

        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString ();
            HotelDoc hotelDoc = JSON.parseObject (sourceAsString , HotelDoc.class);
            //System.out.println(hotelDoc);


            Map<String, HighlightField> highlightFields = hit.getHighlightFields ();
            HighlightField highlightField = highlightFields.get ("name");
            if (highlightField != null) {
                Text[] fragments = highlightField.getFragments ();
                if (fragments != null) {
                    String highLightName = fragments[0].toString ();
                    hotelDoc.setName (highLightName);
                }
            }

            Object[] sortValues = hit.getSortValues ();
            if (sortValues != null && sortValues.length > 0) {
                hotelDoc.setDistance (sortValues[0]);
            }


            list.add (hotelDoc);
        }
        return new PageResult (total , list);
    }

    @Override
    public List<String> getSuggestions(String prefix) {
        try {
            // 1.准备Request
            SearchRequest request = new SearchRequest("hotel");
            // 2.准备DSL
            request.source().suggest(new SuggestBuilder ().addSuggestion(
                    "suggestions",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .prefix(prefix)
                            .skipDuplicates(true)
                            .size(10)
            ));
            // 3.发起请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 4.解析结果
            Suggest suggest = response.getSuggest();
            // 4.1.根据补全查询名称，获取补全结果
            CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
            // 4.2.获取options
            List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
            // 4.3.遍历
            List<String> list = new ArrayList<>(options.size());
            for (CompletionSuggestion.Entry.Option option : options) {
                String text = option.getText().toString();
                list.add(text);
            }
            return list;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveOrUpdateEs(String jsonString) {
        Hotel hotel = JSON.parseObject(jsonString, Hotel.class);
        HotelDoc hotelDoc = new HotelDoc(hotel);

        IndexRequest request = new IndexRequest("hotel"); //索引库名称
        request.id(hotel.getId().toString());
        String jsonStr = JSON.toJSONString(hotelDoc);
        request.source(jsonStr, XContentType.JSON);
        try {
            IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void deleteEs(String id) {
        DeleteRequest request = new DeleteRequest("hotel",id); //索引库名称
        try {
            DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
